#include <ClanLib/core.h>
#include <ClanLib/application.h>
#include <ClanLib/display.h>
#ifdef USE_SDL
	#include <ClanLib/sdl.h>
#else
	#include <ClanLib/gl.h>
#endif
#include "entity.h"

// Entity

Entity::Entity()
{
	pos = CL_Pointf(0,0);
	oldPos = pos;
	sprite = new CL_Sprite();
	size = CL_Size();
}

Entity::Entity(CL_Sprite &sprite_copy)
{
	pos = CL_Pointf(0,0);
	oldPos = pos;
	sprite = new CL_Sprite(sprite_copy);
	size = CL_Size(sprite->get_width(),sprite->get_height());
}

Entity::Entity(CL_Sprite &sprite_copy, CL_Pointf &initial_position)
{
	pos = initial_position;
	oldPos = pos;
	sprite = new CL_Sprite(sprite_copy);
	size = CL_Size(sprite->get_width(),sprite->get_height());
}

Entity::~Entity()
{
	free(sprite);
}

void Entity::update(Viewport &viewport, Level &level)
{
	float time = sprite->update();
	physics(time, level);
	logic(time);
	sprite_frame = sprite->get_current_frame();
	dest = CL_Rect(CL_Point(pos), sprite->get_frame_size(sprite_frame));
	viewport.draw((*sprite), dest);
}

inline void Entity::physics(float &time, Level &level)
{
	// Update position and velocity
	vel += accel * time;
	pos.x += vel.x * time;
	pos.y += vel.y * time;

	onGround = false;

	// Update tile id
	tile = level.get_tile_id(pos);
	int distance = (pos.x-oldPos.x)*(pos.x-oldPos.x) + (pos.x-oldPos.y)*(pos.y-oldPos.y);

	// Collision code: (only valid for objects less than 2 tiles x 2 tiles)
	// If entity is not within a solid tile and is moving at a safe velocity
	if(!level.get_tile_solid(tile) && distance < (level.get_smallest_tiledim()*level.get_smallest_tiledim())){
		// Get tile details (solidity of adjacent tiles and tile area)
		char adjacent = level.get_tile_adjacent(tile);
		tileArea = level.get_tile_area(tile);

		// Get distances to edges from center the entity area
		vOffset = size.height/2;
		hOffset = size.width/2;
		CL_Rectf entityArea = CL_Rectf(pos.x-hOffset,pos.y-vOffset,pos.x+hOffset,pos.y+vOffset);

		// Check basic hit cases (up,down,left,right)
		if((adjacent&2) == 2 && entityArea.top < tileArea.top) hitRoof();
		if((adjacent&64) == 64 && entityArea.bottom > tileArea.bottom) hitFloor();
		if((adjacent&8) == 8 && entityArea.left < tileArea.left) hitLeftWall();
		if((adjacent&16) == 16 && entityArea.right > tileArea.right) hitRightWall();
		entityArea = CL_Rectf(pos.x-hOffset,pos.y-vOffset,pos.x+hOffset,pos.y+vOffset);

		// Check top left hit case
		if((adjacent&1) == 1 && entityArea.left < tileArea.left && entityArea.top < tileArea.top){
			if(vel.y < 0){ // If moving upward
				// Get x intercept
				float xIntercept;
				if(entityArea.top != tileArea.top){
					float yDiff = entityArea.top - tileArea.top;
					float xDiff = (pos.x-oldPos.x)*yDiff/(pos.y-oldPos.y);
					xIntercept = entityArea.left - xDiff;
				} else xIntercept = entityArea.left;

				// Work out whch edge was hit
				if(xIntercept < tileArea.left) hitRoof(); // Hit bottom of roof tile
				else hitLeftWall(); // Hit right hand edge of tile to the left

			} else hitLeftWall(); // Otherwise must have hit right hand edge of tile to the left
		}

		// Check top right hit case
		if((adjacent&4) == 4 && entityArea.right > tileArea.right && entityArea.top < tileArea.top){
			if(vel.y < 0){ // If moving upward
				// Get x intercept
				float xIntercept;
				if(entityArea.top != tileArea.top){
					float yDiff = entityArea.top - tileArea.top;
					float xDiff = (pos.x-oldPos.x)*yDiff/(pos.y-oldPos.y);
					xIntercept = entityArea.right - xDiff;
				} else xIntercept = entityArea.right;

				// Work out whch edge was hit
				if(xIntercept > tileArea.right) hitRoof(); // Hit bottom of roof tile
				else hitRightWall(); // Hit left hand edge of tile to the right

			} else hitRightWall(); // Otherwise must have hit left hand edge of tile to the right
		}

		// Check bottom left hit case
		if((adjacent&32) == 32 && entityArea.left < tileArea.left && entityArea.bottom > tileArea.bottom){
			if(vel.y > 0){ // If moving downward
				// Get x intercept
				float xIntercept;
				if(entityArea.bottom != tileArea.bottom){
					float yDiff = entityArea.bottom - tileArea.bottom;
					float xDiff = (pos.x-oldPos.x)*yDiff/(pos.y-oldPos.y);
					xIntercept = entityArea.left - xDiff;
				} else xIntercept = entityArea.left;

				// Work out whch edge was hit
				if(xIntercept < tileArea.left) hitFloor(); // Hit top of floor tile
				else hitLeftWall(); // Hit right hand edge of tile to the left

			} else hitLeftWall(); // Otherwise must have hit right hand edge of tile to the left
		}

		// Check bottom right hit case
		if((adjacent&128) == 128 && entityArea.right > tileArea.right && entityArea.bottom > tileArea.bottom){
			if(vel.y > 0){ // If moving downward
				// Get x intercept
				float xIntercept;
				if(entityArea.bottom != tileArea.bottom){
					float yDiff = entityArea.bottom - tileArea.bottom;
					float xDiff = (pos.x-oldPos.x)*yDiff/(pos.y-oldPos.y);
					xIntercept = entityArea.right - xDiff;
				} else xIntercept = entityArea.right;

				// Work out whch edge was hit
				if(xIntercept > tileArea.right) hitFloor(); // Hit top of floor tile
				else hitRightWall(); // Hit left hand edge of tile to the right

			} else hitRightWall(); // Otherwise must have hit left hand edge of tile to the right
	}
	} else { //Otherwise perform failsafe collision detection
		// Failsafe collision detection
		bool tooFast = true;
	}

	// Store current position for use in next frame
	oldPos = pos;
}

inline void Entity::hitRoof()
{
	pos.y = tileArea.bottom - vOffset;
	vel.y = 0;
}

inline void Entity::hitFloor()
{
	onGround = true;
	pos.y = tileArea.top + vOffset;
	vel.y = 0;
}

inline void Entity::hitLeftWall()
{
	pos.x = tileArea.left + hOffset;
	vel.x = 0;
}

inline void Entity::hitRightWall()
{
	pos.x = tileArea.right - hOffset;
	vel.x = 0;
}


inline void Entity::logic(float &time)
{
}

CL_Pointf Entity::getPos()
{
	return pos;
}

float Entity::getRot()
{
	return rot;
}

void Entity::setPos(CL_Pointf position)
{
	pos = position;
}

void Entity::setRot(float rotation)
{
	sprite->set_angle(rotation);
	rot = rotation;
}

// EntityManager

EntityManager::EntityManager()
{
}

EntityManager::~EntityManager()
{
}

void EntityManager::draw(Viewport &viewport, Level &level)
{
	std::list<Entity*>::iterator it;

	for(it = entities.begin() ; it != entities.end() ; it++ )
	{
		(*it)->update(viewport, level);
	}
}

void EntityManager::add()
{
	Entity entity;

	//entities.push_back(&entity);
}

void EntityManager::add(Entity &entity)
{
	entities.push_back(&entity);
}

void EntityManager::clear()
{
	entities.clear();
}
